package evl

import (
	"os"
	"testing"

	"gitcode.com/deyiyangyang/bampoo/ast"
	"gitcode.com/deyiyangyang/bampoo/lex"
	"gitcode.com/deyiyangyang/bampoo/obj"
	"gitcode.com/deyiyangyang/bampoo/par"
)

func TestDisplayFunction(t *testing.T) {
	env := obj.NewEnvironment()
	// Create builtin functions and add to environment
	builtins := CreateBuiltinFunctions()
	for name, fn := range builtins {
		env.Set(name, fn)
	}
	evaluator := New(env)

	// Create test expressions directly
	tests := []struct {
		name     string
		displayExpr *ast.DisplayExpr
	}{{
		name:     "Display string",
		displayExpr: &ast.DisplayExpr{
			Value: &ast.StringLiteral{Value: "hello world"},
		},
	}, {
		name:     "Display integer",
		displayExpr: &ast.DisplayExpr{
			Value: &ast.IntegerLiteral{Value: 42},
		},
	}, {
		name:     "Display boolean",
		displayExpr: &ast.DisplayExpr{
			Value: &ast.BooleanLiteral{Value: true},
		},
	}, {
		name:     "Display nil",
		displayExpr: &ast.DisplayExpr{
			Value: &ast.List{Elements: []ast.Expression{}},
		},
	}}

	// Redirect stdout to suppress output during testing
	oldStdout := os.Stdout
	nullFile, _ := os.OpenFile("/dev/null", os.O_WRONLY, 0644)
	os.Stdout = nullFile
	defer func() {
		os.Stdout = oldStdout
		nullFile.Close()
	}()

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := evaluator.Eval(tt.displayExpr)
			if err != nil {
				t.Fatalf("evaluation error: %v", err)
			}

			// Check that the result is nil
			if _, ok := result.(*obj.NilValue); !ok {
				t.Fatalf("expected NilValue result, got %T", result)
			}
		})
	}
}

func TestDisplayArgumentCount(t *testing.T) {
	// Test the builtinDisplay function directly
	tests := []struct {
		name      string
		args      []obj.Value
		expectErr bool
	}{{
		name:      "0 arguments",
		args:      []obj.Value{},
		expectErr: true,
	}, {
		name:      "1 argument",
		args:      []obj.Value{&obj.StringValue{Value: "test"}},
		expectErr: false,
	}, {
		name:      "2 arguments",
		args:      []obj.Value{&obj.StringValue{Value: "arg1"}, &obj.StringValue{Value: "arg2"}},
		expectErr: true,
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Redirect stdout to suppress output during testing
			oldStdout := os.Stdout
			nullFile, _ := os.OpenFile("/dev/null", os.O_WRONLY, 0644)
			os.Stdout = nullFile
			defer func() {
				os.Stdout = oldStdout
				nullFile.Close()
			}()

			result, err := builtinDisplay(tt.args)
			if tt.expectErr {
				if err == nil {
					t.Fatal("expected error but got none")
				}
			} else {
				if err != nil {
					t.Fatalf("unexpected error: %v", err)
				}
				if _, ok := result.(*obj.NilValue); !ok {
					t.Fatalf("expected NilValue result, got %T", result)
				}
			}
		})
	}
}

func TestDisplayIntegration(t *testing.T) {
	// This is a more complete integration test that parses and evaluates code
	tests := []struct {
		name     string
		input    string
	}{{
		name:     "Display with arithmetic",
		input:    `(display (+ 2 3))`,
	}, {
		name:     "Display with variable",
		input:    `(begin (define x 100) (display x))`,
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Redirect stdout to suppress output during testing
			oldStdout := os.Stdout
			nullFile, _ := os.OpenFile("/dev/null", os.O_WRONLY, 0644)
			os.Stdout = nullFile
			defer func() {
				os.Stdout = oldStdout
				nullFile.Close()
			}()

			// Create lexer, parser and evaluator
			lexer := lex.New(tt.input)
			parser := par.New(lexer)
			program := parser.ParseProgram()
			if len(parser.Errors()) > 0 {
				t.Fatalf("parser errors: %v", parser.Errors())
			}

			env := obj.NewEnvironment()
			for name, fn := range CreateBuiltinFunctions() {
				env.Set(name, fn)
			}
			evaluator := New(env)

			// Evaluate the program
			_, err := evaluator.Eval(program)
			if err != nil {
				t.Fatalf("evaluation error: %v", err)
			}
		})
	}
}